﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Data;
using System.IO;
using System.Xml;
using System.Diagnostics;
/*
	Custom view building todo:
	- should we request just the subquery and build it up. I think so
	- test a few scenarios
	- handle errors better.
	- store in config better for multli-line
	- editing. 



	Dev notes
	Changes:
	05/07/2012 - added UserRequestedEdit so presenter can change to edit mode.
	06/07/2012 - implemented data-edit-by default.
	09/07/2012 - Letting go old code

	Move files:
		- use System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData) + \DbView
		- add version to config. assume 1.2 if it is missing
		- double-check upgrade. Need compatibility plus custom.

	Todo: Adhoc

	Todo: Dev database
		- drop views etc?
		- no add index (premature optimisation)
		- 

	Foreign Keys 
		- Check integrity
			alter table Test1 CHECK CONSTRAINT FK_Test1_Test2_Test2_key
			DBCC CHECKCONSTRAINTS ('Test1') 
			alter table Test1 NOCHECK CONSTRAINT FK_Test1_Test2_Test2_key
			or 
			select Test2_key, count(Test2_key) as number
			from Test1 where not exists (select [key] from Test2 where [key] = Test2_key)
			group by Test2_key
		- Refactor all the execute code to be table data driven

user schemas
select * 
  from sys.schemas
where name not in (select name from sys.syslogins)
 and name not in (select name from sys.database_principals )


	***** Bugs: *****
	- Create table does not create non-null columns
	- can modify column to make it non null. just add a default constraint then drop it after.
	- table is not restored in fields when you go back from another non-table grid.
	- cant remove a column with a default. Needs drop default command.

	- delete table. When you delete the bottom table it throws. Not always. Maybe the last two?
	- switching tabs when the field selector is displayed trashes the field selector.
		- nearly there but there is some tab-switching logic that is messing stuff up.
			- works for existing tab switching, but not for new connetion new tab.
	- All new code needs to be tested with a schema.

*/

namespace DbView
{
	/*
		Interface for the main form
		Split into facets? ie more interfaces
	*/
	interface IMainView
	{
		// a real application finish.
		void Shutdown();
		// threaded data fetch notifications
		void DataFetchStarted();
		void DataFetchFinished(bool completed);
		// show results.
		void ResetGrid(DataTable table, bool isReadOnly, int rowIndex);
		void AdhocQueryComplete(DataTable table);
		// filter rows displayed
		void ApplySearchFilter(String searchText);
		// Detach/Attach search list so presenter can maintin the dropdown history.
		void DetachSearchListControl();
		void AttachSearchListControl(List<String> theList, string text);
		// Detach/Attach available views.
		void DetachViewListControl();
		void AttachViewListControl(List<String> theList);
		void ShowViewName(String viewName);
		// reset the search filter display.
		void ResetSearchFilter(String searchText);
		void OnNewConnection(ConnectionInfo ci);  // used to fix-up adhoc window
		// attach popup context menu to the grid
		void UpdatePopup(DbViewDriver.CustomMenuItem menu);
		// dialog displays
		void ShowSource(String objectName, string theSource, string searchTerm);
		bool ShowViewEditDialog(Model model, ViewDefinition viewDef);
		bool ShowConnectionDialog(Model model);
		bool ShowDriverDialog(Model model);
		bool ShowViewFilterForm(ViewDefinition currentView, string fieldName, string fileValue);
		bool ShowSourceSearchDialog(SearchSourceParams dialogParams);

		bool ShowImportDialog(DbViewDriver.IDatabaseExportSource DbDriver, string tableName, string fileName);
		bool ShowExportDialog(DbViewDriver.IDatabaseExportSource DbDriver, string tableName, string fileName, ViewDefinition viewDef);

		void ShowAdhocQueryWindow(ConnectionInfo ci, DbViewDriver.IDataSource dataSource);
		// for information 
		void UpdateGui(ConnectionInfo ci, Commands.Cmnd command, string tableName, ViewDefinition currentView);
		// message box. either Y/N or Y/N/Cancel - return 0 = N; 1 = Y; -1 = can.
		int AskUser(String question, string caption, bool cancelOptionIsAvailable);
		// message box. either info or error
		void InformUser(String message, string caption, bool IsError);
		// gets a parent window object; may be Winforms, WPF, null. It's up to the driver to figure it out.
		object WindowFrame { get; }
		// Work around detecting edit changes. This should transfer the data in the current edit cell 
		// to the table. return true to continue false to stay in grid
		bool FinaliseEdits();
		// Set edit mode of grid
		void SetEditMode(bool enableEdit);
		// Set refresh rate (0 = off)
		void SetRefreshRate(int refreshRateMs);
	}

	class MainPresenter
	{
		// interfaces to other components
		IMainView m_View;			// thin view
		// DbViewDriver.IDataSource this.m_Source;		// data-fetcher
		// The M of MVP!
		Model m_Model;

		// Stuff
		//Dictionary<popupindex, PopupConfig.Handler> this.m_Popups;
		// is this better in the model? Think about it if 
		DbViewStateHistory m_History = new DbViewStateHistory();

		enum ImportExport { Export, Import }

		// state
		/*
			Current command is in a state of flux at the moment. Before the command upgrade
			the only commands were ones that changed the grid. Thus the current command actually
			holds the status of the grid. Since the upgrade I've intriduced commands that just do
			stuff. If I update the current command with this info then I suspect functions will break.
			OTOH stuff may break anyway. For now this is the current grid command.
		*/
		Command m_CurrentCommand = new Command(Commands.Cmnd.Extension, "", Command.CommandAction.poptable);
		DataTable m_Table;				// bowing to the inevitable
		DataTable m_CurrentRowTable;	// stores the currently selected row in its own table (may not be necessary now)
		string m_TrackTable = "";       // new way of tracking the table name
		int m_RestoredRowIndex = 0;		// selected row index when relevane
		bool m_WantRestoreRowIndex = false; // flag for row index restoration
		ViewDefinition m_CurrentView;
		string m_SearchText = "";
		string m_SourceSearchText = "";
		string m_AdhocText = "";
		bool m_Connected = false;
		EditState m_EditState = new EditState(null);
		DataFetchWorker m_ActiveThread = null; // only one thread may be active. Others are assumed cancelled
		Command m_RefreshedCommand = null;     // holds the command that was active when the refresh button was pressed
		string m_RefreshConnection = "";	   // holds the connection that was active when the refresh button was pressed

		public MainPresenter()
		{
			this.m_Model = new Model(); // eachg presenter holds it's own model now
		}

		// Clone operation. Careful now. We need copies of the members rather than
		// shared references
		public MainPresenter Clone()
		{
			MainPresenter theClone = new MainPresenter();
			// here with fresh one - fix up the members
			// Share the main view. The caller can reset this
			theClone.m_View = this.m_View;
			// should clone the model, but for now use secret knowledge.
			// set the driver and connection
			string connectionTag = this.m_Model.CurrentConnection;
			ConnectionInfo ci = this.m_Model.ConnectionList[connectionTag];
			theClone.Model.SetConnection(ci);
			// attempt to get away without history. This may change
			// DbViewStateHistory this.m_History = new DbViewStateHistory();
			// state
			theClone.m_CurrentCommand = this.m_CurrentCommand;
			if (this.m_CurrentRowTable != null)
			{
				theClone.m_CurrentRowTable = this.m_CurrentRowTable.Clone();
				// get the row from this to the clone
				theClone.m_CurrentRowTable.ImportRow(this.m_CurrentRowTable.Rows[0]);

				theClone.m_RestoredRowIndex = this.m_RestoredRowIndex; // Probably not important
				theClone.m_WantRestoreRowIndex = this.m_WantRestoreRowIndex; // Probably not important
			}
			// required for fields drilldown from table data
			// ViewDefinition  this.m_m_CurrentView;  // MAY CAUSE PROBLEMS
			theClone.m_TrackTable = this.m_TrackTable; // new table tracking method.

			theClone.m_SearchText = this.m_SearchText; // Probably not important
			// string this.m_SourceSearchText = "";
			// bool this.m_Connected = false;
			// List<String> this.m_SearchList;
			// EditState this.m_EditState = new EditState(null); // Probably not important
			//// DataFetchWorker this.m_ActiveThread = null;       // Very transitory. Only non-null when fetching data

			return theClone;
		}

		// initialisation. Called by Main()
		public void attachView(IMainView view) { this.m_View = view; }

		/////////////////////////////////////////
		// Api used by main form
		public Command CurrentCommand { get { return this.m_CurrentCommand; } }
		public Model Model { get { return this.m_Model; } }

		public void InitialiseMru(MruMenu mruMenu)
		{
			// remove missing Connections
			//ConnectionListMgr cl = this.m_Model.ConnectionList;
			for (int idx = mruMenu.NumEntries - 1; idx >= 0; --idx)
			{
				string strName = mruMenu.GetFileAt(idx);
				try
				{
					// connInfo ci = cl[strName];
					// above causes mru connections from other drivers to be stripped
					//
					ConnectionInfo ci = Model.ConnectionList.GetConnectionInfo(strName);
					if (ci == null) mruMenu.RemoveFile(strName);
				}
				catch (DbViewApplicationException)
				{
					mruMenu.RemoveFile(strName);
				}
			}
			// bit hacky. Set the current connection to the most recent used so that
			// it is selected on first show of the connection dialog.
			if (mruMenu.NumEntries > 0)
			{
				string strName = mruMenu.GetFileAt(0);
				// connInfo ci = cl[strName];
				// above causes crash because driver is unknown
				try
				{
					ConnectionInfo ci = Model.ConnectionList.GetConnectionInfo(strName);
					this.m_Model.SetConnection(ci);
				}
				catch (DbViewApplicationException)
				{
					// not sure what to do here
				}
			}
		}

		public void EndSession() { this.m_Model.SaveConfig(true); }

		public bool UserRequestedExit()
		{
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return false;
			this.m_View.Shutdown();
			return true;
		}
		// wire this up to the window closing event in the form
		public bool IsOkToClose()
		{
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return false;
			return true;
		}

		// View is used for the first time
		// retuern t/f depending on whether the connection was made.
		public bool MakeInitialConnection(String initialConnectionTag)
		{
			// check for auto launch with a connection
			if (initialConnectionTag.Length > 0)
			{
				try
				{
					this.ResetModelConnectionString(initialConnectionTag);
					this.UpdateUIOnNewConnection();
					return true;
				}
				catch (DbViewApplicationException) { }
			}
			// normal: request a connection
			// 
			this.m_Connected = this.GetConnectionFromUser();
			// show tables 
			if (this.m_Connected) this.UpdateUIOnNewConnection();
			return this.m_Connected;
		}

		public bool UserRequestedNewTab()
		{
			// check edits and bail the operation if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return false;
			return true;
		}

		public bool UserRequestedConnect(String connectionTag)
		{
			// check edits and bail the operatio if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return false;

			this.ResetModelConnectionString(connectionTag);
			this.m_Connected = true;
			if (this.m_Connected) this.UpdateUIOnNewConnection();
			return this.m_Connected;
		}

		public void UserRequestedCancelFetch()
		{
			if (this.m_ActiveThread == null) return; // too late
			// ask?
			// deactivate the thread and inform the view
			this.m_ActiveThread = null;
			this.m_View.DataFetchFinished(true);
			// restore state. spoof the method until history is rewritten

			//String memo = this.m_History.NavigateBack(false);
			//DbViewState state = new DbViewState(memo);
			DbViewState state = this.m_History.NavigateBack(false);

			// 1st time in there is nowhere to navigate back to 
			if (state != null)
			{
				this.m_CurrentCommand = state.Command;
				ConnectionInfo ci = this.m_Model.ConnectionList[state.ConnectionTag];
				if (ci != null)
					this.m_Model.SetConnection(ci);
			}
		}

		public bool UserRequestedConnect()
		{
			// check edits and bail the operatio if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return false;

			this.m_Connected = this.GetConnectionFromUser();
			if (this.m_Connected) this.UpdateUIOnNewConnection();
			return this.m_Connected;
		}

		public void UserReactivatedView(bool reExecuteCommand)
		{
			// trap 1st entry case
			if (this.m_History.IsEmpty) return;

			// reset the source in the driver.
			// The model keeps the local connection, but as the driver is
			// a singleton then this needs refreshing.
			// this is a hack.
			this.m_Model.ConnectionList.CurrentDriverName = this.m_Model.DataSource.DriverName;
			this.m_Model.SetCurrentConnection(this.m_Model.GetCurrentConnection());
			//
			List<String> views = this.ResetViewList();
			bool wantView = (views.IndexOf(this.m_CurrentView.Name) == -1) ? false : true;

			// test current view here and switch
			if (reExecuteCommand)
			{
				// branch hopefully not used. It will make a Db call for each
				// tab switch, which isn't snappy
				this.m_WantRestoreRowIndex = true;
				if (wantView)
					this.UserSelectedAView(this.m_CurrentView.Name);
				else
					// redo the report: safest way to get the screen up to date.
					this.UserRequestedCommand(this.m_CurrentCommand, this.m_SearchText);
			}
			else
			{
				// No repeat the command. The form has reattached the data table to the grid view.
				// this just restores configuration that was lost in the tab switch.
				// 1. Recreate and reattach the correct context menu to the grid
				this.SetPopupMenu();
				// 2. If this is a named view from the drop down reset the display
				if (wantView)
					this.m_View.ShowViewName(this.m_CurrentView.Name);
				// 2a. Set filter text if possible.
				this.m_View.ResetSearchFilter(this.m_SearchText);
				// 3. Update the rest of the GUI
				this.m_View.UpdateGui(this.m_Model.ConnectionList[this.m_Model.CurrentConnection], this.m_CurrentCommand.Cmd, this.m_CurrentView.TableName, this.m_CurrentView);
			}
			// always turn off refresh
			this.m_View.SetRefreshRate(0);
		}

		// legacy from the main menu 
		// This version does not add the 1st column of the grid to the command
		public void UserRequestedCommand(Commands.Cmnd commandtype, string searchText)
		{
			Command command = new Command(commandtype);
			if (command.Action == Command.CommandAction.poptable)
				this.UpdateGridViewContent(command, searchText, null);
			else
				this.UserRequestedCommand(command, searchText);
		}

		// This version developed to handle pop-ups which usually require some 
		// data fron the context of the current selected row.
		public void UserRequestedCommand(Command command, string searchText)
		{
			try
			{
				// Add params. 
				this.AddCommandParams(command);

				// [Specialise: add new system action functionality ]
				// Non table or source code popping command in application eg. ShowCommand
				if (command.Action == Command.CommandAction.sysaction)
				{
					switch (command.Cmd)
					{
						case Commands.Cmnd.ShowCommand: this.ShowCommand(); break;
						case Commands.Cmnd.SearchSource:
							// assume the object name is in column 0
							this.UserRequestedSearchSource(command[Command.TableParameterName(0)]);
							break;
						case Commands.Cmnd.EditView:
							this.UserRequestedViewEdit(this.m_CurrentView.Name);
							break;
						case Commands.Cmnd.ShowProfileData:
							this.ShowProfileData(command);
							break;
						case Commands.Cmnd.Import:
							this.UserRequestedExportImport(this.TableFromCommand(command), ImportExport.Import);
							break;
						case Commands.Cmnd.Export:
							this.UserRequestedExportImport(this.TableFromCommand(command), ImportExport.Export);
							break;
						case Commands.Cmnd.Excel:
							this.UserRequestedExportToExcel(false);   // false to disallow csv option
							break;
						case Commands.Cmnd.Extension:
							this.DoSystemAction(command);
							break;
					}
				}

				// Non table or source code popping option defined by driver.
				// we need to pass the drive the command and a bit of environment.
				// 
				if (command.Action == Command.CommandAction.driveraction)
				{
					bool RefreshGrid;
					// This call is a bit hacky
					// pass in a window object to act as a parent if the command needs to show a dialog.
					// pass in the model for all sorts of useful stuff
					// the out param is set to t/f depending on whether or not the grid needs refreshing 
					// or return a non-trivial command.
					Command nextCommand = this.Model.DataSource.ExecuteDriverAction(command, this.m_View.WindowFrame, this.Model, searchText, out RefreshGrid);
					if (nextCommand.Cmd == Commands.Cmnd.Nothing)
					{
						if (RefreshGrid)
							// this repeats the current command by simulating entry of the same text 
							// into the search bar.
							this.UserRequestedSearch(this.m_SearchText);
					}
					else
					{
						this.UserRequestedCommand(nextCommand, "");
						return;
					}
				}

				// may need to hook special-purpose ones in some cases
				// changes the grid
				if (command.Action == Command.CommandAction.poptable)
				{
					this.UpdateGridViewContent(command, searchText, null);
				}

				// not generic enough yet
				// need to distinguish View object source from show sql
				// pops up the source display dialog
				if (command.Action == Command.CommandAction.text)
					// ShowSource();
					this.ShowSource(command);
			}
			catch (Exception exc)
			{
				this.m_View.InformUser(exc.Message, "Error during execution", true);
			}
		}

		// why never needed before
		// hunt around for table. prefer an explicit parameter. Otherwise 
		// fall back to column 0
		private string TableFromCommand(Command command)
		{
			if (command["TABLE"] != null)
				return command["TABLE"];
			return command[Command.TableParameterName(0)];
		}

		// command implemented only by the frame.
		//
		void DoSystemAction(Command command)
		{
			switch (command.Name)
			{
				case "GOTO": this.ShowHistoricView(); break;
				case "TMPL_VIEW": this.UserRequestedShowTemplateCode(); break;
				case "TMPL_ADD": this.UserRequestedAddTemplateCode(); break;
				case "TMPL_DEL": this.UserRequestedRemoveTemplateCode(); break;
			}
		}

		// mechnism for passing data from the current grid row 
		// to the pop-up command.
		// 1. always pass the first column as that is usually all that is needed
		// 2. check and add other columns marked by the driver code.
		// 3. Add the table name to hack cases where the table is needed
		//    but cannot be deduced from the data table
		private void AddCommandParams(Command command)
		{
			DataRow dr = (this.m_CurrentRowTable.Rows.Count > 0) ? this.m_CurrentRowTable.Rows[0] : null;
			if (dr != null && dr[0] != null)
			{
				string key = Command.TableParameterName(0);
				string val = dr[0].ToString();
				command.AddParam(key, val);

				// check other rows for parameter mark-up
				// Drivers can mark up the columns of the datatable objects
				// they create to indicate columns used as parameters in the contect nenu
				// features they supply. Here the data from the current selected row
				// is set as the value of the parameter of the marked column in the command.
				DataTableExtraInfoManager extraInfo = new DataTableExtraInfoManager(dr.Table);
				foreach (DataColumn dc in dr.Table.Columns)
				{
					string paramName = extraInfo.Param(dc);
					if (paramName.Length > 0)
						command.AddParam(paramName, dr[dc].ToString());
				}
			}

			// hacky way of passing table
			command.AddParam("TABLE", this.GetTableName());
		}

		public void UserRequestedHistory(String searchText)
		{
			this.UpdateGridViewContent(new Command(Commands.Cmnd.History), searchText);
		}

		public void UserRequestedProfile(String searchText)
		{
			this.UpdateGridViewContent(new Command(Commands.Cmnd.Profile), searchText);
		}

		public void UserRequestedViewData()
		{
			this.ShowProfileData(new Command(Commands.Cmnd.ShowProfileData));
		}

		public void UserRequestedTemplates(String searchText)
		{
			this.UpdateGridViewContent(new Command(Commands.Cmnd.Templates), searchText);
		}

		// from [ GO ] button and elsewhere
		public void UserRequestedSearch(String searchText)
		{
			// check edits and bail the operatio if [Cancel] was pressed
			// 
			bool saveRequiresConfirmation = !(Model.DataSource.IsDevelopmentDatabase);
			if (this.CommitEdits(saveRequiresConfirmation) == false) return;
			// 
			UpdateSearchList(searchText);
			// if the current command is managed by this class get the grid to filter as
			// all rows are present otherwise re-run the command with the new filter.
			if (this.IsProcessedByPresenter(this.m_CurrentCommand))
				this.m_View.ApplySearchFilter(searchText);
			else
				this.UpdateGridViewContent(this.m_CurrentCommand, searchText, this.m_CurrentView);
		}

		public void UpdateSearchList(String searchText)
		{
			// check search text against the saved list of text searched for in the past
			// and bring the instance to the top if it has already been searched for.
			// Otherwise just stick it at the top
			this.m_View.DetachSearchListControl();
			int textLoc = PresenterSharedState.instance.SearchList.IndexOf(searchText);
			if (textLoc != -1)
				PresenterSharedState.instance.SearchList.RemoveAt(textLoc);
			if (searchText.Length > 0)
				PresenterSharedState.instance.SearchList.Insert(0, searchText);
			this.m_View.AttachSearchListControl(PresenterSharedState.instance.SearchList, searchText);
		}
		
		public void UpdateSearchText(String searchText)
		{
			this.m_SearchText = searchText;
		}
		
		public void UserRequestedRefresh(String searchText)
		{
			// Skip anything if the datafetch thread is busy
			// Scenario the refresh rate is faster than the data fetch operation
			// or the user is generating a new report.
			if (this.m_ActiveThread != null) return;

			// Selecting a new report should disable the timer,
			// hence test against the refresh command and take the appropriate action
			bool isValidForRefresh = (this.m_RefreshedCommand != null &&
										this.m_RefreshedCommand.Cmd == this.m_CurrentCommand.Cmd &&
										this.m_RefreshConnection == this.m_Model.CurrentConnection);
			// either issue the command to refresh the grid.
			if (isValidForRefresh)
				this.UserRequestedSearch(searchText);
			// or abort and instruct the view to turn off the timer.
			else
				this.CheckStopRefresh();  // at this point we know the refresh needs to be stopped
		}

		// Here from above and from main request processing 
		private void CheckStopRefresh()
		{
			if (this.m_RefreshedCommand == null) return; // not activated
			if (this.m_RefreshedCommand.Cmd == this.m_CurrentCommand.Cmd &&
				this.m_RefreshConnection == this.m_Model.CurrentConnection)
				return; // it's wanted
			// not wanted; ie refresh command is not the same as current command
			// inform view to desist
			this.m_View.SetRefreshRate(0);
			// set semi-official state in presenter to imply that grid refresh is off
			this.m_RefreshedCommand = null;
		}

		public void UserRequestedViewEdit(String viewDefKey)
		{
			// check edits and bail the operatio if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return;

			ViewDefinition Definition = ViewDefinitionManager.FindView(viewDefKey, this.m_Model.CurrentConnection);
			if (Definition == null) return; // huh?
			if (this.m_View.ShowViewEditDialog(this.m_Model, Definition))
			{
				Definition.SetOwner(this.m_Model.CurrentConnection, true);
				ViewDefinitionManager.AddOrReplace(Definition, this.m_Model.CurrentConnection);
				this.m_Model.SaveConfig(false);
				this.ResetViewList();
				this.UserRequestedSearch("");
			}
		}

		internal void UserRequestManageLookups()
		{
			object o = this.m_Model.DataSource.TryToGetOptionalInterface(DbViewDriver.OptionalInterface.Lookup);
			if (o != null)
			{
				DbViewDriver.ILookup lookupIface = o as DbViewDriver.ILookup;

				LookupForm dlg = new LookupForm(lookupIface, this.m_Model.DataSource.DriverName, this.Model.CurrentConnection);
				dlg.ShowDialog();
				return;
			}
			else
			{
				this.m_View.InformUser("The current database driver does not support this feature", "Manage Lookups", false);
			}
		}

		public void UserRequestedCustomViewFromAdHoc(bool IsAdHocShown)
		{
			// Ultra-preconditions: The Adhoc window must be showing and 
			// there must be SQL in it. Otherwise tell the user about this.
			// (via the CustomViewBuilder)
			if (!IsAdHocShown || this.m_AdhocText.Length == 0)
			{
				this.m_View.InformUser(CustomViewBuilder.GetInstructionsOnFailedPreconditions(), "Cannot proceed", true);
				return;
			}

			string sql = this.m_AdhocText;
			CustomViewBuilder viewBuilder = new CustomViewBuilder();
			string message;
			if (viewBuilder.CheckSql(sql, out message) == false)
			{
				this.m_View.InformUser(message, "Could not create custom view", true);
				return;
			}

			ViewDefinition Definition = viewBuilder.MakeDefinition(sql, Model.DataSource, Model.CurrentConnection, out message);

			if (Definition == null)
			{
				this.m_View.InformUser(message, "Could not create custom view", true);
				return;
			}

			Definition.SetOwner(this.m_Model.CurrentConnection, true);
			ViewDefinitionManager.AddOrReplace(Definition, this.m_Model.CurrentConnection);
			this.m_Model.SaveConfig(false);
			this.ResetViewList();

			string successMessage = @"
Custom view {0} has been created and added to the
dropdown list above. To change the view name, order of columns
or sort order select {0} from the drop down list
then select Data, Modify DataView or press the icon to the left
of the filter funnel.";

			this.m_View.InformUser(String.Format(successMessage, Definition.Name), "Success", false);
			//UserRequestedSearch("");
		}

		// when user selects a view from the drop down
		public void UserSelectedAView(String viewName)
		{
			// check edits and bail the operatio if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return;

			ViewDefinition Definition = ViewDefinitionManager.FindView(viewName, this.m_Model.CurrentConnection);
			if (Definition == null) return;
			// update state
			this.m_CurrentView = Definition;
			this.m_TrackTable = this.m_CurrentView.TableName; // try this. Restore tracked table from view.
			// persist the view name
			Command cmd = new Command(Commands.Cmnd.TableData);
			this.UpdateGridViewContent(cmd, "", this.m_CurrentView);

			// TODO: turn editing on here if possible for dev databases 
			if (Model.DataSource.IsDevelopmentDatabase)
				this.UserRequestedEdit(true);
		}

		// need the type as a param
		public void UserRequestedAggregate(GridCellInfo cellInfo)
		{
			/*
				string message = string.Format("{0} {1} {2}", cellInfo.ColumnName, cellInfo.Data, cellInfo.DataType.ToString());
				// create a special view.
				 this.m_m_CurrentView = ViewDefintionFromCellInfo(cellInfo);
				// pass into the 
				UpdateGridViewContent(Commands.cmd.Aggregate, "",  this.m_m_CurrentView);
				*/
			this.UserRequestedAggregate(cellInfo, new Command(Commands.Cmnd.Aggregate));
		}

		public void UserRequestedAggregate(GridCellInfo cellInfo, Command command)
		{
			string message = string.Format("{0} {1} {2}", cellInfo.ColumnName, cellInfo.Data, cellInfo.DataType.ToString());
			// create a special view.
			ViewDefinition viewDef = this.ViewDefintionFromCellInfo(command, cellInfo);
			viewDef.Name = string.Format("{0}.{1}.{2}", command.ToString(), viewDef.TableName, cellInfo.ColumnName);
			// need to persist it somehow for history
			ViewDefinitionManager.AddOrReplace(viewDef, this.m_Model.CurrentConnection);
			// extract a view name if any from the current view
			if (this.m_CurrentView.Name != this.m_CurrentView.TableName)
				command.AddParam("VIEWNAME", this.m_CurrentView.Name);
			// add the column name to the command
			command.AddParam("FIELD", cellInfo.ColumnName);
			// pass into the 
			this.m_CurrentView = viewDef;
			this.UpdateGridViewContent(command, "", this.m_CurrentView);
		}

		public void UserRequestedLocationOfConfigFile()
		{
			string messageTemplate = @"
The configuration file that holds details of connections and 
user defined views is:

{0}

Note: if you intend to modify configuration manually then:
1) Close all open instances of the application otherwise your 
   changes will be overridden.
2) Please make a back-up of the file before you change anything
   as the application cannot open a configuration file that 
   is not valid Xml.

Would you like to open the configuration folder in explorer? 
";
			// future: open explorer in
			string message = string.Format(messageTemplate, DbView.Resources.ConfigDoc.Instance.FileName);
			if (this.m_View.AskUser(message, "Config filename", false) == 1)
			{
				// ask user if they want to open the folder and do it
				string exeFile = "explorer.exe";
				string exeParams = Path.GetDirectoryName(DbView.Resources.ConfigDoc.Instance.FileName);
				ProcessRunner pr = new ProcessRunner(exeFile);
				pr.ShellExec(exeParams);
				if (pr.LastProcessErrored)
				{
					message = string.Format("The command:\n {0} {1}\n caused the following error:\n{2}", exeFile, exeParams, pr.Output);
					this.m_View.InformUser(message, "Could not launch explorer", true);
				}
			}
		}

		public void UserRequestedOpenHelp()
		{
			// Hard-code for now. Name and location (folder DOCUMENTATION under app path"
			string helpFileName = "DbViewSharpUserGuide.pdf";
			string helpFilePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\DOCUMENTATION";

			string fullHelpFileName = Path.Combine(helpFilePath, helpFileName);
			string message = "";
			if (File.Exists(fullHelpFileName))
			{
				// future: open explorer in
				// ask user if they want to open the folder and do it
				ProcessRunner pr = new ProcessRunner(fullHelpFileName);
				pr.ShellExec("");
				if (pr.LastProcessErrored)
				{
					message = string.Format("The operation caused the following error:\n{0}", pr.Output);
					this.m_View.InformUser(message, "Could not display user guide", true);
				}
			}
			else
			{
				message = string.Format("User guide file [ {0} ] is not in folder\n{1}", helpFileName, helpFilePath);
				this.m_View.InformUser(message, "Could not display user guide", true);
			}
		}



		// create a "view" to encapsulate all the information required for the aggregate
		// function. 
		ViewDefinition ViewDefintionFromCellInfo(Command command, GridCellInfo cellInfo)
		{
			string tableName = this.GetTableName(); // looks in the current view def; ie. the one being drilled from
			string key = string.Format("{0}.{1}.{2}", command.ToString(), tableName, cellInfo.ColumnName);
			ViewDefinition viewdef = ViewDefinitionManager.FindView(key, this.m_Model.CurrentConnection);
			if (viewdef == null)
			{
				viewdef = ViewDefinitionManager.makeNewViewDefinition();
				viewdef.Name = key;  // the only way for back to reconnect with the viewdef
				ViewDefinitionManager.AddOrReplace(viewdef, this.m_Model.CurrentConnection);
			}
			viewdef.TableName = tableName;
			// copy the filter clause and extra
			viewdef.ExtraClause = this.m_CurrentView.ExtraClause;
			viewdef.FilterClause = this.m_CurrentView.FilterClause;
			// above is experimental. Remove it if it causes problems.
			// create a single schema field representing the column clicked on for aggregation
			string fieldType = SchemaField.TranslateSystemType(cellInfo.DataType.ToString());
			SchemaField aggField = new SchemaField(cellInfo.ColumnName, fieldType);
			viewdef.AddField(aggField);
			return viewdef;
		}

		// original to support the back button
		public void UserRequestedBack()
		{
			// check edits and bail the operatio if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return;

			bool viewWasSaved = this.IsWantedInHistory(this.m_CurrentCommand);
			this.UserRequestedBack(viewWasSaved);
		}

		// new entry point for Driver actions that refresh the grid.
		public void UserRequestedBack(bool viewWasSaved)
		{
			// check edits and bail the operatio if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return;
			DbViewState prevState = this.m_History.NavigateBack(viewWasSaved);
			if (prevState != null)
				this.ShowPreviousView(prevState);
		}


		// called from the back button popup menu. 
		public void UserRequestedBack(string prevStateMemo)
		{
			// check edits and bail the operatio if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return;

			bool viewWasSaved = this.IsWantedInHistory(this.m_CurrentCommand);
			DbViewState prevState = this.m_History.NavigateBackTo(prevStateMemo, viewWasSaved);

			this.ShowPreviousView(prevState);
		}

		public bool UserRequestedEdit(bool enableEdit)
		{
			// check here whether the table is editable
			if (!this.IsDataEditable()) return false; // always edit off
			// test and save ant edits when toggling out of edit mode
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false)
				// right: user was asked to save edits and cancelled. 
				// Ergo: edit mode was true. The return of this is edit state 
				// so return true = I still want to be editing
				return true;

			// create an edit state
			this.m_EditState = new EditState((enableEdit) ? this.m_Table : null);
			// return the state to prove sucess
			this.m_View.SetEditMode(enableEdit);
			return enableEdit;
		}
		/*
				public void SetEditMode(bool isEditable, DataTable dt)
				{
					this.m_EditState = new EditState((isEditable) ? dt : null);
				}
		*/

		// extract logic from populate method. For now only grid generated by 
		// Commands.cmd.TableData are editable. TODO: expand for profile edits.
		// made public for now as main form uses it.
		public bool IsDataEditable()
		{
			bool DataIsEditable = (this.m_CurrentCommand.Cmd == Commands.Cmnd.TableData);
			return DataIsEditable;
		}

		// 
		// get this to work for main menu selection or right-click
		// Main menu: input string is empty. Use saved string and deduce the objects to search
		// Popup: we have an object name to search for. Assume all and skip the dialog.
		public void UserRequestedSearchSource(string searchText)
		{
			bool fromPopup = (searchText.Length > 0); // this is a proxy test for hitting this code from the pop-up
			// check edits and bail the operatio if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return;

			// 
			// set dialog params depending on the request context (menu or pop-up)
			SearchSourceParams.SearchTarget tgt = (fromPopup) ? SearchSourceParams.SearchTarget.All
															: this.DeduceSourceSearchtarget();
			string searchFor = (fromPopup) ? this.CheckExtractSchema(searchText) : this.m_SourceSearchText;
			SearchSourceParams dialogParams = new SearchSourceParams(tgt, searchFor, this.GetSearchObjectList());
			if (!fromPopup)
			{
				// pop the dialog
				if (!this.m_View.ShowSourceSearchDialog(dialogParams))
					return; // user decided not to
			}
			// decide how to proceed 
			Commands.Cmnd command = this.DeduceCommand(dialogParams.Target);
			this.m_SourceSearchText = dialogParams.SearchText;
			Command searchCommand = new Command(command);
			searchCommand.AddParam("SEARCH", this.m_SourceSearchText);
			// 2nd param should be the filter not the search term
			this.UpdateGridViewContent(searchCommand, "");
		}




		private SearchSourceParams.SearchTarget[] GetSearchObjectList()
		{
			// query the current driver for the source search operations supported
			List<SearchSourceParams.SearchTarget> searchSupportedList = new List<SearchSourceParams.SearchTarget>();
			this.AddIfSupported(searchSupportedList, SearchSourceParams.SearchTarget.Tables, Commands.Cmnd.TableFieldSearch);
			this.AddIfSupported(searchSupportedList, SearchSourceParams.SearchTarget.Views, Commands.Cmnd.ViewSourceSearch);
			this.AddIfSupported(searchSupportedList, SearchSourceParams.SearchTarget.Procs, Commands.Cmnd.ProcedureSourceSearch);
			this.AddIfSupported(searchSupportedList, SearchSourceParams.SearchTarget.Funcs, Commands.Cmnd.FunctionSourceSearch);
			this.AddIfSupported(searchSupportedList, SearchSourceParams.SearchTarget.Calcs, Commands.Cmnd.ComputedSourceSearch);
			this.AddIfSupported(searchSupportedList, SearchSourceParams.SearchTarget.All, Commands.Cmnd.AllSourceSearch);
			// AddIfSupported(searchSupportedList, SearchSourceParams.SearchTarget.JobSteps, Commands.cmd.Nothing);
			return searchSupportedList.ToArray();
		}

		private void AddIfSupported(List<SearchSourceParams.SearchTarget> searchSupportedList, SearchSourceParams.SearchTarget searchTarget, Commands.Cmnd cmd)
		{
			if (this.m_Model.DataSource.SupportsCommand(cmd)) searchSupportedList.Add(searchTarget);
		}

		// Adventureworks case: for search source from objects prefixed by schemaName.table
		// the views and procs may use the [schemaName].[table] format which will not match using
		// our simple search technique. My solution is to trim off the schema name
		private string CheckExtractSchema(string searchText)
		{
			int iPos = searchText.IndexOf('.');
			if (iPos == -1) return searchText; // n.a. as in the non-schema situation
			return searchText.Substring(iPos + 1); // return the rest
		}

		SearchSourceParams.SearchTarget DeduceSourceSearchtarget()
		{
			Commands.Cmnd cmd = this.m_CurrentCommand.Cmd;
			return
			(cmd == Commands.Cmnd.Procedures) ? SearchSourceParams.SearchTarget.Procs :
			(cmd == Commands.Cmnd.ProcedureSourceSearch) ? SearchSourceParams.SearchTarget.Procs :
			(cmd == Commands.Cmnd.Views) ? SearchSourceParams.SearchTarget.Views :
			(cmd == Commands.Cmnd.ViewSourceSearch) ? SearchSourceParams.SearchTarget.Views :
			(cmd == Commands.Cmnd.Tables) ? SearchSourceParams.SearchTarget.Tables :
			(cmd == Commands.Cmnd.TableFieldSearch) ? SearchSourceParams.SearchTarget.Tables :
			(cmd == Commands.Cmnd.Functions) ? SearchSourceParams.SearchTarget.Funcs :
			(cmd == Commands.Cmnd.FunctionSourceSearch) ? SearchSourceParams.SearchTarget.Funcs :
			(cmd == Commands.Cmnd.Computed) ? SearchSourceParams.SearchTarget.Calcs :
			(cmd == Commands.Cmnd.ComputedSourceSearch) ? SearchSourceParams.SearchTarget.Calcs :
			(cmd == Commands.Cmnd.AllSourceSearch) ? SearchSourceParams.SearchTarget.All :
			SearchSourceParams.SearchTarget.Procs;
		}

		Commands.Cmnd DeduceCommand(SearchSourceParams.SearchTarget tgt)
		{
			return
			(tgt == SearchSourceParams.SearchTarget.Procs) ? Commands.Cmnd.ProcedureSourceSearch :
			(tgt == SearchSourceParams.SearchTarget.Views) ? Commands.Cmnd.ViewSourceSearch :
			(tgt == SearchSourceParams.SearchTarget.Tables) ? Commands.Cmnd.TableFieldSearch :
			(tgt == SearchSourceParams.SearchTarget.Funcs) ? Commands.Cmnd.FunctionSourceSearch :
			(tgt == SearchSourceParams.SearchTarget.Calcs) ? Commands.Cmnd.ComputedSourceSearch :
			(tgt == SearchSourceParams.SearchTarget.All) ? Commands.Cmnd.AllSourceSearch :
			Commands.Cmnd.ProcedureSourceSearch;
		}

		// Status panet click or some other UI to quickly reset the filter 
		public void UserRequestedClearFilter()
		{
			this.CheckClearAggregateCallerParam();
			this.m_CurrentView.FilterClause = "";
			this.UpdateGridViewContent(this.m_CurrentCommand, this.m_SourceSearchText, this.m_CurrentView);
		}

		public void UserRequestedFilter(GridCellInfo cellInfo)
		{
			string fieldName = (cellInfo != null) ? cellInfo.ColumnName : "";
			string cellValue = GetCellFilterValue(cellInfo);
			if (this.m_View.ShowViewFilterForm(this.m_CurrentView, fieldName, cellValue))
			{
				// handle the case where the user changes or clears the filter following
				// a drilldown into an aggregated report. In this case the CALLER parameter
				// in the current command is set to aggregation. This causes the filter to
				// be re-applied whatever.
				// this clones the current command without the caller as there
				// is no provision to remove or change a command parameter.
				this.CheckClearAggregateCallerParam();
				this.UpdateGridViewContent(this.m_CurrentCommand, this.m_SourceSearchText, this.m_CurrentView);
			}
		}

		private string GetCellFilterValue(GridCellInfo cellInfo)
		{
			// can't do anythin more if the input is null
			if (cellInfo == null) return "";

			// Dates: US/UK will confuse SQL so convert to unambiguous
			if (cellInfo.DataType == typeof(DateTime))
			{
				DateTime dt;
				// if the parse fails we can't do anything better than return the string unmodified.
				if (!DateTime.TryParse(cellInfo.Data, out dt))
					return cellInfo.Data;
				// but with a date time string....
				string format = "yyyy-MM-dd";  // OK if no time element
				if ((dt.Hour + dt.Minute + dt.Second) > 0) // ie not 00:00:00
					format += " HH:mm:ss";				// Note the big HH. little hh will cause grief.
				
				return dt.ToString(format);
			}

			// non-date types return the string unmodifies.
			return cellInfo.Data;
			
		}

		// called from menu for use when a view is changed in such a way that it never works 
		// and thus cannot be fixed
		public void UserRequestedFixViews()
		{
			string message = @"
This utility procedure will check for any DataViews that have generated
an error when trying to produce a grid and attempt to fix them
by removing: 

a. The filter which may cause a syntax error or timeout.
b. The sort fields, which may cause a timeout.
c. Any fields apparently not belonging to the table and more.

Any view that is changed will be reported for you to check when this
operation has finished.

Press Yes if you wish to continue, No otherwise.
";
			if (this.m_View.AskUser(message, "Proceed with DataView Fix", false) == 0)
				return;
			// save to write all the verified view flags to disk
			DbView.Resources.ConfigDoc.Instance.SaveConfig();
			// do this just because at the moment the export source interface exposes a more modern schema fetch method
			DbViewDriver.IDatabaseExportSource IData = this.m_Model.DataSource.DatabaseExportSource;
			IData.ConnectionString = this.m_Model.DataSource.ConnectionString;
			// find views which are unverified
			List<String> names = ViewDefinitionManager.FindViewListForConnection(this.m_Model.CurrentConnection);
			foreach (string viewKey in names)
			{
				ViewDefinition viewDef = ViewDefinitionManager.FindView(viewKey, this.m_Model.CurrentConnection);
				if (!viewDef.IsVerified) // not verified so potentially in error
				{
					if (viewDef.AttemptFix())
					{
						message = string.Format("Modified DataView: {0}", viewDef.Name);
						this.m_View.InformUser(message, "DataView Fix Attempt", false);
					}
				}
				if (viewDef.IsPermanent && IData != null)
				{
					if (viewDef.IsCustom)
					{
						message = string.Format("Skipping attempted fix of custom view: {0}", viewDef.Name);
						this.m_View.InformUser(message, "DataView Fix Attempt", false);
					}
					else
					{
						DbView.FieldInfo[] schema = IData.GetSchema(viewDef.TableName);
						if (viewDef.AttemptFix(schema))
						{
							message = string.Format("Modified DataView: {0}", viewDef.Name);
							this.m_View.InformUser(message, "DataView Fix Attempt", false);
						}
					}
				}
			}
			DbView.Resources.ConfigDoc.Instance.SaveConfig();
		}


		private void CheckClearAggregateCallerParam()
		{
			if (this.m_CurrentCommand["CALLER"].Length > 0) // for now its enough there is a param
			{
				Command clone = new Command(DbView.Commands.Cmnd.TableData);
				// transfer existing parameters.
				clone.AddParam("TABLE", this.m_CurrentCommand["TABLE"]);
				clone.AddParam(Command.TableParameterName(0), this.m_CurrentCommand[Command.TableParameterName(0)]);
				this.m_CurrentCommand = clone;
			}
		}

		public void UserRequestedAdhocQueryWindow()
		{
			string connectionTag = this.m_Model.CurrentConnection;
			ConnectionInfo ci = this.m_Model.ConnectionList[connectionTag];
			this.m_View.ShowAdhocQueryWindow(ci, this.m_Model.DataSource);
		}

		public void UserRequestedShowTemplateCode()
		{
			// not quite right
			ITemplateEditForm editForm = new TemplateEditForm();
			TemplateCollection templates = new TemplateCollection();
			templates.LoadFile(this.m_Model.TemplateFile);
			string templateTitle = this.GetObjectName(); // title
			Template t = templates[templateTitle];
			editForm.EditItem = t;
			if (editForm.ShowForm())
			{
				templates.SaveFile(this.m_Model.TemplateFile);
				this.UserRequestedTemplates(this.m_SearchText);
			}
		}

		public void UserRequestedAddTemplateCode()
		{
			// not quite right
			ITemplateEditForm editForm = new TemplateEditForm();
			TemplateCollection templates = new TemplateCollection();
			templates.LoadFile(this.m_Model.TemplateFile);
			editForm.EditItem = templates.NewTemplate();
			if (editForm.ShowForm())
			{
				templates.SaveFile(this.m_Model.TemplateFile);
				this.UserRequestedTemplates(this.m_SearchText);
			}
		}

		public void UserRequestedRemoveTemplateCode()
		{
			// not quite right
			TemplateCollection templates = new TemplateCollection();
			templates.LoadFile(this.m_Model.TemplateFile);
			string templateTitle = this.GetObjectName(); // title
			Template t = templates[templateTitle];
			templates.Remove(t);
			templates.SaveFile(this.m_Model.TemplateFile);
			this.UserRequestedTemplates(this.m_SearchText);
		}

		// history popup only 
		public void ShowHistoricView()
		{
			// assumes the current table is a history so we'll ask the history 
			// object to figure it out
			DbViewState prevState = this.m_History.StateFromHistoryRow(this.m_CurrentRowTable.Rows[0]);
			this.ShowPreviousView(prevState);
		}


		public void UserRequestedNewInstance()
		{
			Process p = null;
			try
			{
				// more shameless hacking from code project
				string targetDir = System.Windows.Forms.Application.ExecutablePath;
				p = new Process();
				p.StartInfo.WorkingDirectory = targetDir;
				p.StartInfo.FileName = targetDir; //Application.ExecutablePath;
				// use current connection
				string arg1 = this.m_Model.CurrentConnection;
				p.StartInfo.Arguments = string.Format("\"{0}\"", arg1);
				p.StartInfo.CreateNoWindow = false;
				p.Start();
			}
			catch (SystemException ex)
			{
				throw new DbViewApplicationException("Could not launch new instance", ex);
			}
		}

		// Distinguish from the actual request to refresh triggered by the timer
		public void UserRequestedAutoRefresh(int refreshRateMs)
		{
			// grab the current command
			this.m_RefreshedCommand = new Command(this.m_CurrentCommand.Cmd);
			this.m_RefreshConnection = this.m_Model.CurrentConnection;
			// ask the view to start timed requests.
			this.m_View.SetRefreshRate(refreshRateMs);
		}

		// 
		//public void UserRequestedImport(String tableName)
		//{
		//    // check edits and bail the operatio if [Cancel] was pressed
		//    if (CommitEdits(SaveRequiresConfirmation()) == false) return;
		//    // get the specialist interface from the datasource
		//    DbViewDriver.IDatabaseExportSource DbDriver = this.m_Model.DataSource.DatabaseExportSource;
		//    // if null then inform  use
		//    if (DbDriver == null)
		//    {
		//        this.m_View.InformUser("The driver does not support this operation", "Error", true);
		//        return;
		//    }
		//    // make the default import file name
		//    // Show the dialog
		//}

		// 
		private void UserRequestedExportImport(String tableName, ImportExport direction)
		{
			// check edits and bail the operatio if [Cancel] was pressed
			// Probably not relevant as this is driven from the table right-click
			// if (CommitEdits(SaveRequiresConfirmation()) == false) return;

			// get the specialist interface from the datasource
			DbViewDriver.IDatabaseExportSource DbDriver = this.m_Model.DataSource.DatabaseExportSource;
			// if null then inform  use
			if (DbDriver == null)
			{
				this.m_View.InformUser("The driver does not support this operation", "Error", true);
				return;
			}
			// Set the connection string. 
			DbDriver.ConnectionString = this.m_Model.DataSource.ConnectionString;
			// make the default import file name
			//String appFolder = "DbView"; // selected for legacy naming reasons.
			string dataPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
			string defaultFileName = Path.Combine(dataPath, tableName + ".CSV");
			// Show the dialog
			if (direction == ImportExport.Export)
			{
				// test this.m_CurrentCommand and use  this.m_m_CurrentView instead 
				if (this.m_CurrentCommand.Cmd == Commands.Cmnd.Tables)
					this.m_View.ShowExportDialog(DbDriver, tableName, defaultFileName, null);
				else
				{
					defaultFileName = this.MakeDefaultFileName(dataPath, tableName, this.m_CurrentView, ".CSV");
					this.m_View.ShowExportDialog(DbDriver, tableName, defaultFileName, this.m_CurrentView);
				}
			}
			else
			{
				// clear the file name and force the user to choose
				if (!File.Exists(defaultFileName) || new FileInfo(defaultFileName).Length == 0)
					defaultFileName = "";
				this.m_View.ShowImportDialog(DbDriver, tableName, defaultFileName);
				this.UserRequestedCommand(Commands.Cmnd.Tables, this.m_SearchText);
			}
		}
		// export from table data only
		string MakeDefaultFileName(String path, string table, ViewDefinition viewDef, string ext)
		{
			// prefer the view name unless it's hooky.
			try
			{
				if (viewDef != null)
					return Path.Combine(path, this.m_CurrentView.Name + ext);
			}
			catch (ArgumentException)
			{
			}
			return Path.Combine(path, table + ext);
		}
		
		public void UserRequestedExportToExcel(bool allowCSV)
		{
			// check for the 500-row data case and warn about missing data.
			// if [ Yes ] to continue is pressed then carry on. Otherwise do not.
			if (!this.CheckShowExcelDataWarning())
				return;
			// Get command
			string sql = this.GetCurrentCommandString();

			ConnectionInfo ci = this.m_Model.GetCurrentConnection();
			UserRequestedExportToExcel(this.m_Table, this.m_CurrentView.Name, ci, sql, allowCSV);

		}
		
		public void UserRequestedExportToExcel(DataTable dt, String viewName, ConnectionInfo ci, string sql, bool allowCSV)
		{
			// get database
			string database = string.Format("{0}.{1}", ci.Server, ci.Name);
			
			// Get documents data path
			string dataPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
			// build a wacky default wacky output name
			
			string fileName = String.Format("{0}_{1}_{2}.XLS", ci.Server, ci.Database, viewName);
			// replace file breaking characters in the string with _
			fileName = fileName.Replace("/","_").Replace("\\", "_").Replace(":", "_");
			fileName = Path.Combine(dataPath, fileName);
			// send data off to be exported.
			ExportExcelForm dlg = new ExportExcelForm(dt, viewName, fileName, database, "", sql);
			// pass on the csv option
			dlg.SetCSVControlState(allowCSV);
			dlg.ShowDialog();
		}

		private bool CheckShowExcelDataWarning()
		{
			// only ever for table data
			if (this.m_CurrentCommand.Cmd != Commands.Cmnd.TableData) return true;
			// if the rows in the table = the cut-off then very likly there will be rows missing.
			if (this.m_Table.Rows.Count == 500)  // hm. big assumption here
			{
				string message = @"
Note: Only rows displayed in the grid will be exported to Excel. The 
number of rows in the grid would indicate that it is very likely that
this is only part of the list you are expecting to be exported. 

In order to export all of the data take the following steps.
1. Press [ No ] to clear this message.
2. Open the Ad-hoc query window.
3. Right click on the grid and select ""Show SQL"". Press [ Copy all ].
4. Paste into the Ad-hoc query window.
5. Remove the text ""Top 500"" and execute the query.
6. Press the Excel toolbar button in the Ad-hoc query window.
Note: you can create a spreadsheet with up to 65,536 rows by this method.

If you wish to continue to export just the data from the grid 
press [ Yes ]. Otherwise [ No ].
";
				int answer = this.m_View.AskUser(message, "Warning not all data may be exported", false);
				return (answer == 1);
			}
			return true;
		}


		//////////////////////////////////////////////////////////////////////////
		// Add new requests just above here
		public void UserRequestedTest()
		{
//DbView.Resources.LookupInfo.Instance.Test(); return;
//object o = this.m_Model.DataSource.TryToGetOptionalInterface(DbViewDriver.OptionalInterface.Schema);
//DbViewDriver.ISchemaInfo iface =  o as DbViewDriver.ISchemaInfo;
//if (iface == null) throw new DbViewApplicationException("No interface");
//string [] tables = iface.GetTables();

//FieldInfo[] afi = iface.GetSchema(tables[0]);

//m_View.InformUser(afi[0].Name, tables[0], false);
//return;

object o = this.m_Model.DataSource.TryToGetOptionalInterface(DbViewDriver.OptionalInterface.Lookup);
if (o != null)
{
	DbViewDriver.ILookup lookupIface = o as DbViewDriver.ILookup;

	LookupForm dlg = new LookupForm(lookupIface, this.m_Model.DataSource.DriverName, this.Model.CurrentConnection);
	dlg.ShowDialog();
	return;
}

			// normal state
			string message = @"
This menu option is reserved for me to try out new features before
wiring them into the system properly. You may get a nice surprise
or a nasty shock, but normally you should see this.
			";

			this.m_View.InformUser(message, "A message from the developer", false);
		}

		public void UserRequestedSelectDriver()
		{
			// check edits and bail the operatio if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return;

			if (this.m_View.ShowDriverDialog(this.m_Model))
			{
				this.UserRequestedConnect();
			}
		}

		//////////////////////////////////////////////////////////////////////////
		// Implementation from here
		private void ShowPreviousView(DbViewState prevState)
		{
			if (this.m_Model.CurrentConnection != prevState.ConnectionTag)
			{
				this.ResetModelConnectionString(prevState.ConnectionTag);
				this.UpdateUIOnNewConnection();
			}
			this.m_CurrentCommand = prevState.Command;
			this.m_RestoredRowIndex = prevState.RowIndex;
			this.m_CurrentView = ViewDefinitionManager.FindView(prevState.ViewDefName, this.m_Model.CurrentConnection);
			// try this 
			if (this.m_CurrentView != null) this.m_TrackTable = this.m_CurrentView.TableName;
			//  this.m_WantRestoreRowIndex is used too far down the method chain to pass as a param
			// when navigating back it is nice to reset the cell/row selection. It mimics nrowser
			// back behaviour better. 
			this.m_WantRestoreRowIndex = true;
			this.UserRequestedSearch(prevState.Filter);
		}

		/*
			just change the model connection string. Don't actually do anything
		*/
		private void ResetModelConnectionString(String connectionTag)
		{
			ConnectionInfo ci = this.m_Model.ConnectionList.GetConnectionInfo(connectionTag);
			if (ci != null)
			{
				// this will reset the driver if necessary
				this.m_Model.SetConnection(ci);
			}
			else
			{
				// connection no longer valid
				string message = string.Format("Connection {0} is no longer defined", connectionTag);
				throw new DbViewApplicationException(message);
			}
		}

		////////////////////////////////////////////////////////////
		// notification of significant changes in controls
		public void RowSelected(DataRow dr, int rowIndex)
		{
			if (this.m_CurrentRowTable != null)
			{
				this.m_CurrentRowTable.Rows.Clear();
				if (dr != null)
					this.m_CurrentRowTable.ImportRow(dr);
			}
			// need to update history now.
			if (this.IsWantedInHistory(this.m_CurrentCommand))
			{
				this.m_RestoredRowIndex = rowIndex;
				this.m_History.UpdateRowIndex(this.m_CurrentCommand, rowIndex);
			}
			// if this is a table name tracker then update the table name
			int tableIndex = this.TableNameIndex();
			if (tableIndex >= 0 && dr != null && dr[0] != null)
			{
				this.m_TrackTable = dr[tableIndex].ToString();
			}
			// another hack for the aggregate filter. This rally is an awkward feature
		}
		////////////////////////////////////////////////////////////////////
		// Notification of change of adhoc text
		public void AdHocCommandTextChanged(String text)
		{
			this.m_AdhocText = text;
		}
		////////////////////////////////////////////////////////////////////
		// Notification of click on a columns
		public void UserSortedView(String sortColumn)
		{
			// get the view
			// modify the sort
			// done.
			if (sortColumn.Length == 0) return;
			// relax this later
			// if (this.m_CurrentCommand.Cmd != Commands.cmd.TableData) return;
			string viewDefKey = this.m_CurrentView.Name;
			ViewDefinition Definition = ViewDefinitionManager.FindView(viewDefKey, this.m_Model.CurrentConnection);
			if (Definition == null) return; // can't do much
			// not if the view is user-defined 
			if (Definition.IsPermanent) return;

			if (Definition.SortFields.GetLength(0) > 1) return; // already sorted. Relax this to allow for 1.
			// now it's worth checking.
			// process SortInfo ie +/- column name
			string ad = sortColumn.Substring(0, 1);
			string sortField = sortColumn.Substring(1);
			foreach (SchemaField field in Definition.SchemaFields)
			{
				if (sortField == field.Name)
					Definition.ReplaceSort(field, (ad == "-"));
			}
		}

		//
		// For some cases we need to know the physical table. This is tricky to 
		// keep track of in some cases. So this routine passes back the column index of the
		// table name for any grid that displays it eg: tables.
		// Otherwise returns -1
		private int TableNameIndex()
		{
			if (this.m_CurrentCommand.Cmd == Commands.Cmnd.Tables) return 0;
			if (this.m_CurrentCommand.Cmd == Commands.Cmnd.TableFieldSearch) return 0;
			if (this.m_CurrentCommand.Cmd == Commands.Cmnd.Views) return 0;
			// this is a hack for custom commands such as Table sizes. 
			// Name the returned DataTable "Tables" and put the table in column 0
			// future development should require commands to register the table name column
			// as a parameter. 
			if (this.m_CurrentRowTable != null)
				if (this.m_CurrentRowTable.TableName == Commands.Cmnd.Tables.ToString()) return 0;
			return -1;
		}

		public string GetTableName()
		{
			string tableNameOld = this.GetTableNameDeprecated();
			if (tableNameOld != this.m_TrackTable)
			{
				// NEW: trust the new version
				tableNameOld = this.m_TrackTable;
			}
			// Required during debugging, but this feature not being debugged now. Either fixed or low impact.
			//			else
			////				Console.WriteLine("{0} OK", tableNameOld);

			return tableNameOld;
		}

		// a little hacky - assumes knowledge of the tables view
		public string GetTableNameDeprecated()
		{
			// Table name from tables row.
			if (this.m_CurrentCommand.Cmd == Commands.Cmnd.Tables ||
				this.m_CurrentCommand.Cmd == Commands.Cmnd.TableFieldSearch ||
				this.m_CurrentCommand.Cmd == Commands.Cmnd.Views ||  // hm. not really a table. This may cause problems, but solves the view data problem
				(this.m_CurrentRowTable != null && this.m_CurrentRowTable.TableName == Commands.Cmnd.Tables.ToString()))
				if ((this.m_CurrentRowTable != null && this.m_CurrentRowTable.Rows.Count > 0))  // there must be a current row selected
					return this.m_CurrentRowTable.Rows[0][0].ToString();
			// try the view def or finally the 
			// the goal is to trust the tracktable member to replace all of this horror.
			return (this.m_CurrentView == null) ? this.m_TrackTable : this.m_CurrentView.TableName;
		}

		// a little hacky - assumes knowledge of the tables view
		public string GetObjectName()
		{
			//DbView_Base DbView = DbViewFactory.ViewFactory.GetDbView(this.m_CurrentCommand.Cmd);
			//int objectNameColumn = DbView.ObjectNameColumn;
			//if (objectNameColumn == -1) return "";
			//return this.m_CurrentRowTable.Rows[objectNameColumn][0].ToString();
			return this.m_CurrentRowTable.Rows[0][0].ToString();
		}

		public String[] GetHistoryMemoStrings(out int currentItem)
		{
			return this.m_History.GetHistoryMemoStrings(out currentItem);
		}

		////////////////////////////////////////////////////////////////////////////////////////////
		// implementation from here
		////////////////////////////////////////////////////////////////////////////////////////////

		private void UpdateUIOnNewConnection()
		{
			this.ResetViewList();
			// on an new request the system displays all tables
			this.UpdateGridViewContent(new Command(Commands.Cmnd.Tables), "");
			// clear any lingering filter text
			this.m_View.ResetSearchFilter("");
			// todo: fix ad-hoc window
			string connectionTag = this.m_Model.CurrentConnection;
			ConnectionInfo ci = this.m_Model.ConnectionList[connectionTag];
			this.m_View.OnNewConnection(ci);
		}

		private List<String> ResetViewList()
		{
			// on an new request the system repopulates available views
			this.m_View.DetachViewListControl();
			List<String> views = ViewDefinitionManager.FindSavedViewListForConnection(this.m_Model.CurrentConnection);
			this.m_View.AttachViewListControl(views);
			return views;
		}

		/*
			Pops up the connection dialog
		*/
		private bool GetConnectionFromUser()
		{
			// it may be null?
			if (this.m_Model.DataSource == null)
			{
				DbViewDriver.IDataSource driver = this.GetStartupDriver();
				this.m_Model.SetDriver(driver);
			}
			this.m_Model.ConnectionList.CurrentDriverName = this.m_Model.DataSource.DriverName;

			return this.m_View.ShowConnectionDialog(this.m_Model);
		}

		private DbViewDriver.IDataSource GetStartupDriver()
		{
			// try find last used
			DbView.ConnectionInfo ciLastUsed = this.m_Model.GetLastUsedConnection();
			string PluginInterface = "DbViewDriver.IDataSource";
			// should select from the last saved driver.
			List<object> aol = PluginManager.Instance.GetPlugins(PluginInterface);
			// use first loaded driver if: a. there is only one driver
			// b. there is no last connection detected
			// (c. the driver for the last use connection cannot be matched)
			// No. This may not tally with my documentation I'm going to override this 
			// rule if I find the Sql Server Driver
			int idx = 0;
			DbViewDriver.IDataSource driver = aol[idx] as DbViewDriver.IDataSource;
			// If there is a last used then search for a match
			if (ciLastUsed != null)
			{
				for (idx = 0; idx < aol.Count; ++idx)
				{
					DbViewDriver.IDataSource driver2 = aol[idx] as DbViewDriver.IDataSource;
					if (driver2.DriverName == ciLastUsed.Driver)
						driver = driver2;
				}
			}
			// else its the very first time in so search for the Sql Server driver
			else
			{
				for (idx = 0; idx < aol.Count; ++idx)
				{
					DbViewDriver.IDataSource driver2 = aol[idx] as DbViewDriver.IDataSource;
					if (driver2.DriverName.ToUpper() == "SQL SERVER")
						driver = driver2;
				}
			}
			return driver;
		}

		// Assembles info from the presenter into a parameter for the command builder.
		ViewContext CreateViewContext(Command command, string searchText)
		{
			ViewContext vc = new ViewContext(command);
			// vc.CallerCommand = this.m_CurrentCommand;    
			//vc.CallerCurrentRow = this.m_CurrentRowTable;
			// default is not special definition. Table data views will override this
			vc.ViewDef = null;
			vc.FilterString = searchText;
			return vc;
		}

		private void UpdateGridViewContent(Command command, string searchText)
		{
			this.UpdateGridViewContent(command, searchText, null);
		}

		private void UpdateGridViewContent(Command command, string searchText, ViewDefinition viewDef)
		{
			// check edits and bail the operatio if [Cancel] was pressed
			if (this.CommitEdits(this.SaveRequiresConfirmation()) == false) return;

			// Check whether the current command is different to the last refresh command and turn autorefresh
			// off if necessary
			this.CheckStopRefresh();

			// Check for case where user switches command, but witout clearing search text first
			// in the majority of cases they would prefer the text to be cleared. 
			// switch of command. false value of second param is historical value. It probably won't matter what actual value it has
			if (!command.IsSameAs(this.m_CurrentCommand, false))
			{
				if (searchText == this.m_SearchText && searchText.Length > 0) // no new search term entered.
				{
					searchText = "";			// trigger the clear
					this.m_View.ResetSearchFilter(searchText); // refresh the form
				}
				// speculatively keep the row index for the back button
				this.m_History.UpdateRowIndex(this.m_CurrentCommand, this.m_RestoredRowIndex);
			}

			// create the view before updating held command
			ViewContext vc = this.CreateViewContext(command, searchText);
			vc.PreviousCommand = this.m_CurrentCommand; // preserves the previous command info longer this way
			// Create a DbView for managing view defs and popups (one day)
			// DbView_Base DbView = DbViewFactory.ViewFactory.GetDbView(command.Cmd);
			// if view def passed in then use it otherwise build it.
			if (viewDef != null)
				vc.ViewDef = viewDef;
			else
				vc.ViewDef = this.GetViewDefinition(vc);

			// special hack in for Aggregation
			if (command.Cmd == Commands.Cmnd.TableData)
				this.CheckAddAggregationFilter(vc);

			// set command
			this.m_CurrentCommand = command;
			this.m_SearchText = searchText;

			// create an object to manage this.
			this.PopulateGrid(vc);
			// Not if threaded
			// PostGridPopulate();
		}

		ViewDefinition GetViewDefinition(ViewContext vc)
		{
			// Use the viewdef name if it is present
			string key = (vc.ViewDef != null) ? vc.ViewDef.Name : "";
			// next try the command name
			if (key.Length == 0)
			{
				// special case. If this is aggregate and we are here without a view
				// we unfortunately have to hunt back in history to find the key to the view
				// One example is re-activating a tab with an aggregrate.
				if (vc.Command.Cmd == Commands.Cmnd.Aggregate)
					key = this.GetAggregateViewdefKeyFromHistory(); // phew!
				// new idea: look up previous view for table selected.
				if (vc.Command.Cmd == Commands.Cmnd.TableData)
					key = vc.Command["TABLE"];
				else
					key = vc.Command.ToString();
			}
			// TODO: Review for Working with views (see aggregate fiter action)
			return ViewDefinitionManager.FindView(key, this.m_Model.CurrentConnection);
		}

		private string GetAggregateViewdefKeyFromHistory()
		{
			string key = "";
			int c;
			String[] astrMemo = this.GetHistoryMemoStrings(out c);
			foreach (String memo in astrMemo)
			{
				DbViewState state = this.m_History.StateFromMemoString(memo);
				if (state.Command.Cmd == Commands.Cmnd.Aggregate)
				{
					return state.ViewDefName;
				}
			}
			return key;
		}

		// This *only* for drilling into an aggregate report line
		private void CheckAddAggregationFilter(ViewContext vc)
		{
			if (vc.Command.Cmd != Commands.Cmnd.TableData) return; // n.a.
			string tableName = vc.Command["TABLE"];
			if (tableName.Length == 0) return; // can't do anything
			string paramVal = vc.Command["CALLER"];
			if (paramVal.Length > 0) // for now its enough there is a param
			{
				// get the view def and update context
				string viewKey = this.m_CurrentCommand["VIEWNAME"]; // try for a view name
				if (viewKey.Length == 0) viewKey = tableName;  // fall back on table name
				vc.ViewDef = ViewDefinitionManager.FindView(viewKey, this.m_Model.CurrentConnection);
				this.AddAggregateFilterToViewDef(vc);
			}
		}

		void AddAggregateFilterToViewDef(ViewContext vc)
		{
			//
			// To get around problems with the code below when redisplaying an already
			// correctly filtered list test whethter the filter is set already and abort if it is.
			// if this still causes problems then the solution needs reinventing.

			// actually doesn't clear in the case of aggregate, drill, back, move row, drill
			// need to distinguish this case from a redisplay case,

			// if we're not definitely drilling in from the aggregate report 
			if (this.m_CurrentCommand.Cmd != Commands.Cmnd.Aggregate)
				// them preserve the filter clause
				if (vc.ViewDef.FilterClause.Length > 0) return;

			// extract information to build the filter clause from the current selected row
			string fieldName = this.m_CurrentRowTable.Columns[0].ColumnName;
			string value = this.m_CurrentRowTable.Rows[0][0].ToString();
			// Simple clause (SQL supports '' around numbers, but we could be more sophisticated if necessary eg dates
			// ahem. This is the driver's responsibility.
			string clause = string.Format("{0} = '{1}'", fieldName, this.CheckModifyDateString(value));
			// special case: null rows
			if (value.Length == 0) clause = string.Format("NULLIF({0},'') is NULL", fieldName);
			vc.ViewDef.FilterClause = clause;
		}

		private string CheckModifyDateString(string value)
		{
			DateTime tgt;
			if (!DateTime.TryParse(value, out tgt)) return value; // nothing we can do
			// doesn't work for dates with times
			return tgt.ToString("yyyyMMdd HH:mm:ss");
		}

		private void DataFetchProgress(WinFormThreading.ThreadProcessState connectionState)
		{
			DataFetchWorker thread = (DataFetchWorker)connectionState.UserData;
			if (thread != this.m_ActiveThread) return;

			if (connectionState.IsErrored)
			{
				this.m_View.DataFetchFinished(false); // finish before displaying error
				this.m_View.InformUser(connectionState.Exception.Message, "Data fetch error", true);
				this.m_ActiveThread = null; // clear this 
			}
			if (connectionState.IsDone)
			{
				// get the dset somehow.
				this.PostGridPopulate(thread.Results, thread.Context);
				this.m_View.DataFetchFinished(true); // finish after popping the grid in case the UI does something bizarre
				this.m_ActiveThread = null; // clear this 
			}
		}

		void PostGridPopulate(DataTable results, ViewContext vc)
		{
			int rowIndex = (this.m_WantRestoreRowIndex) ? this.m_RestoredRowIndex : 0; // restore if back button
			this.m_WantRestoreRowIndex = false; // disable: this won't be accurate enough for all scenarios

			this.m_Table = results;

			// Turns out the method above finds it difficult to detect the commands to set readonly
			// So we'll do it here "Temporarily" Set to true unless its tabledata
			// above comments are old. If we want to we can add this to the command properties.
			bool isReadOnly = !this.IsDataEditable(); // (this.m_CurrentCommand.Cmd != Commands.cmd.TableData);

			this.m_CurrentRowTable = this.m_Table.Clone(); // get the schema TODO: check whether it gets extra info and copy if not.
			// create a table manage for attaching lookup fields.
			DataTableExtraInfoManager infoManager = new DataTableExtraInfoManager(this.m_Table);

			// before we lose track of the dataset use it to create a viewdef if none is defined
			if (vc.ViewDef == null)
			{
				// Build the view def from the table
				ViewDefinition viewDef = ViewDefinitionManager.makeNewViewDefinition(this.m_Table);
				this.SetViewdefName(viewDef);
				// add to store
				ViewDefinitionManager.AddOrReplace(viewDef, this.m_Model.CurrentConnection);
				// clear view combo
				this.m_View.ShowViewName("");
				this.m_CurrentView = viewDef;
			}
			else
			{
				this.m_View.ShowViewName(vc.ViewDef.Name);
				this.m_CurrentView = vc.ViewDef;
				// here we can sign off the view as having worked.
				this.m_CurrentView.SetTested(true);
				// test for non-table data sort fields on repeated display of a grid
				if (this.m_CurrentCommand.Cmd != Commands.Cmnd.TableData)
				{
					if (this.m_CurrentCommand.Cmd == vc.PreviousCommand.Cmd)
					{
						// only if this is the same command as before
						// don't make user sort too sticky.
						if (vc.ViewDef.SortFields.GetLength(0) > 0)
						{
							// hack something to test
							// DataTableExtraInfoManager m = new DataTableExtraInfoManager(this.m_Table);
							SchemaField sortF = vc.ViewDef.SortFields[0];
							infoManager.SetSorted(this.m_Table.Columns[sortF.Name], (sortF.Ord == "D"));
						}
					}
					else
						vc.ViewDef.ReplaceSort(null, false);
				}
			}
			this.AttachLookupInfo(this.m_Table, this.m_CurrentView, infoManager);
			// move to below above to trap the sort reset
			this.m_View.ResetGrid(this.m_Table, isReadOnly, rowIndex);

			this.SetPopupMenu();

			// save state unless viewing history
			if (this.IsWantedInHistory(this.m_CurrentCommand))
			{
				// most informative selection depends on the command. Override where appropriate
				string tv = (this.m_CurrentCommand.Cmd == Commands.Cmnd.Fields) ? this.m_CurrentView.TableName :
																		 this.m_CurrentView.Name;
				DbViewState currentState = new DbViewState(//m_CurrentCommand.ToString(), 
											this.m_CurrentCommand.ToXml(),  // history pobably needs all the params now.
											this.m_Model.CurrentConnection,
											this.m_SearchText,
											tv,
											0);
				this.m_History.Add(currentState, true);
			}
			else
				this.m_History.Add(null, false);  // register a grid that is not to be saved in history

			this.m_View.UpdateGui(this.m_Model.ConnectionList[this.m_Model.CurrentConnection], this.m_CurrentCommand.Cmd, this.m_CurrentView.TableName, this.m_CurrentView);
		}

		// Version 1.6+ Match fields in the table to lookup SQL
		private void AttachLookupInfo(DataTable dataTable, ViewDefinition viewDef, DataTableExtraInfoManager infoManager)
		{
			String table = viewDef.TableName;
			// only if there is a table to lookup
			if (string.IsNullOrEmpty(table)) return;
			string driver = DbView.Resources.ConnectionListMgr.Instance.CurrentDriverName;
			// Only drivers that expose the lookup interface will use this feature
			// (drivers return null to indicate they do no support lookup)
			Object o = this.m_Model.DataSource.TryToGetOptionalInterface(DbViewDriver.OptionalInterface.Lookup);
			if (o != null)
			{
				// the driver must return an object that implements the interface.
				DbViewDriver.ILookup iface = o as DbViewDriver.ILookup;

				// TODO: There is a better way now using FindkeysForTable
				//foreach (DataColumn dc in dataTable.Columns)
				//{
				//    DbView.Resources.LookupInfo.Key key = new Resources.LookupInfo.Key(this.Model.CurrentDriver, this.Model.CurrentConnection, table, dc.ColumnName);
				//    DbView.Resources.LookupInfo.Data data = DbView.Resources.LookupInfo.Instance.FindData(key);
				//    if (data != null)
				//    {
				//        string command = iface.BuildLookupCommand(data.PkTable,  data.ReportFields, data.PkField, DbView.Resources.LookupInfo.LU_TAG);
				//        if (!String.IsNullOrEmpty(command))
				//            infoManager.SetLookupKey(dc, key.Table, key.Field);
				//    }
				//}
				DbView.Resources.LookupInfo.Key partKey = new Resources.LookupInfo.Key(driver, this.Model.CurrentConnection, table, "");
				DbView.Resources.LookupInfo.Key[] tableKeys = DbView.Resources.LookupInfo.Instance.FindKeysForTable(partKey);
				foreach (DbView.Resources.LookupInfo.Key key in tableKeys)
				{
					DbView.Resources.LookupInfo.Data data = DbView.Resources.LookupInfo.Instance.FindData(key);
					if (data != null)
					{
						DataColumn dc = dataTable.Columns[key.Field];
						if (dc != null)
							infoManager.SetLookupKey(dc, key.Table, key.Field);
					}
				}

			}
		}

		// now used when restoring the datatable following a tab switch
		private void SetPopupMenu()
		{
			DbViewDriver.CustomMenuItem popupMenu = this.Model.DataSource.BuildPopupMenu(this.m_CurrentCommand);
			// presenter may have to supply a menu
			if (this.IsProcessedByPresenter(this.m_CurrentCommand) || // special commands
				popupMenu == null)							// driver delegates responsibility
				popupMenu = this.MakePopupMenu(this.m_CurrentCommand);
			this.m_View.UpdatePopup(popupMenu);

		}

		private DbViewDriver.CustomMenuItem MakePopupMenu(Command command)
		{
			DbViewDriver.CustomMenuItem root = new DbViewDriver.CustomMenuItem(DbViewDriver.CustomMenuItem.ItemType.menu);
			Command customCommand;
			switch (command.Cmd)
			{
				case Commands.Cmnd.History:
					customCommand = new Command(Commands.Cmnd.Extension, "GOTO", Command.CommandAction.sysaction);
					root.AddItem(new DbViewDriver.CustomMenuItem(DbViewDriver.CustomMenuItem.ItemType.item, "Goto", customCommand.ToXml()));
					return root;
				case Commands.Cmnd.Profile:
					this.AddShowProfileData(root);
					return root;
				case Commands.Cmnd.Templates:
					customCommand = new Command(Commands.Cmnd.Extension, "TMPL_VIEW", Command.CommandAction.sysaction);
					root.AddItem(new DbViewDriver.CustomMenuItem(DbViewDriver.CustomMenuItem.ItemType.item, "View/Edit", customCommand.ToXml()));
					DbViewDriver.CustomMenuItem item = new DbViewDriver.CustomMenuItem(DbViewDriver.CustomMenuItem.ItemType.item, "Add",
										new Command(Commands.Cmnd.Extension, "TMPL_ADD", Command.CommandAction.sysaction).ToXml());
					item.IsRowIndependent = true;
					root.AddItem(item);
					root.AddItem(new DbViewDriver.CustomMenuItem(DbViewDriver.CustomMenuItem.ItemType.item, "Delete",
										new Command(Commands.Cmnd.Extension, "TMPL_DEL", Command.CommandAction.sysaction).ToXml()));
					return root;
				default: return this.MakeDefaultPopupMenu();
			}
		}

		private DbViewDriver.CustomMenuItem MakeDefaultPopupMenu()
		{
			DbViewDriver.CustomMenuItem root = new DbViewDriver.CustomMenuItem(DbViewDriver.CustomMenuItem.ItemType.menu);
			root.AddItem(new DbViewDriver.CustomMenuItem(DbViewDriver.CustomMenuItem.ItemType.item, "Profile", DbView.Commands.Cmnd.Profile.ToString()));
			root.AddItem(new DbViewDriver.CustomMenuItem(DbViewDriver.CustomMenuItem.ItemType.item, "Command", DbView.Commands.Cmnd.ShowCommand.ToString()));
			return root;
		}

		void AddShowProfileData(DbViewDriver.CustomMenuItem root)
		{
			DbView.Command command = new DbView.Command(DbView.Commands.Cmnd.ShowProfileData, "", Command.CommandAction.sysaction);
			root.AddItem(new DbViewDriver.CustomMenuItem(DbViewDriver.CustomMenuItem.ItemType.item, "View Data", command.ToXml()));
		}

		private void SetViewdefName(ViewDefinition viewDef)
		{
			switch (this.m_CurrentCommand.Cmd)
			{
				case Commands.Cmnd.TableData: break; // the name is set by the view def dialog
				case Commands.Cmnd.Fields:
					viewDef.Name = string.Format("Schema: {0}", viewDef.TableName);
					break;
				default:
					{
						// Ask table to supply a view name
						string name = this.ViewDefNameFromTable();
						viewDef.Name = (name.Length > 0) ? name : this.m_CurrentCommand.ToString();
					}
					break;
			}
		}

		private string ViewDefNameFromTable()
		{
			DataTableExtraInfoManager ex = new DataTableExtraInfoManager(this.m_Table);
			return ex.GridDescription;
		}

		private void PopulateGrid(ViewContext vc)
		{
			int rowIndex = (this.m_WantRestoreRowIndex) ? this.m_RestoredRowIndex : 0; // restore if back button

			if (this.IsProcessedByPresenter(this.m_CurrentCommand))
			{
				//DataTable data;
				// don't need to go to the data source for history
				if (this.m_CurrentCommand.Cmd == Commands.Cmnd.History)
				{
					this.m_Table = this.m_History.GetHistory();
				}
				else if (this.m_CurrentCommand.Cmd == Commands.Cmnd.Profile)
				{
					string DataOnly = this.m_CurrentCommand["NOTEMPTY"];
					this.m_Table = this.TableFromCurrentRow((DataOnly == "Y"));
				}
				else if (this.m_CurrentCommand.Cmd == Commands.Cmnd.Templates)
				{
					TemplateCollection templates = new TemplateCollection();
					templates.LoadFile(this.m_Model.TemplateFile);
					this.m_Table = templates.MakeTable();
				}
				// do this here as a thread was not needed
				this.PostGridPopulate(this.m_Table, vc);
			}
			else
			{
				// threaded
				WinFormThreading.ThreadProcessState connectionState = new WinFormThreading.ThreadProcessState();
				System.Windows.Forms.ContainerControl huh = this.m_View as System.Windows.Forms.ContainerControl;

				// Create a new thread. This will be the active one.
				// all other threads will be discarded when they return from whereever they have been
				this.m_ActiveThread = new DataFetchWorker(huh, (WinFormThreading.ProgressStateDelegate)this.DataFetchProgress, connectionState);
				// register the thread with the user data returned by the callback
				connectionState.UserData = this.m_ActiveThread;
				// set the params for the action
				try
				{
					// fetchThread.Sql = vb.BuildSql(newState.m_search_text);
					this.m_ActiveThread.Command = this.m_CurrentCommand;
					this.m_ActiveThread.Context = vc;
					this.m_ActiveThread.Model = this.m_Model;
					// connection string we have
					// this.m_CurrentDataThread = fetchThread;
					this.m_ActiveThread.KickOffThread();
					this.m_View.DataFetchStarted();
				}
				catch (NotImplementedException ex)
				{
					this.m_View.DataFetchFinished(false);
					this.m_View.InformUser(ex.Message, "Data Fetch Error", true);
				}
				// turn this into a threaded op
				// this.m_Table = this.m_Model.DataSource.BuildTable(this.m_CurrentCommand, vc);
			}
		}
		// Copied from above: a separate thread for adhoc queries.
		public void RunThreadedAdhocQuery(Command cmd, ViewContext vc)
		{
			// threaded
			WinFormThreading.ThreadProcessState connectionState = new WinFormThreading.ThreadProcessState();
			System.Windows.Forms.ContainerControl huh = this.m_View as System.Windows.Forms.ContainerControl;
			
			// TODO
			// NO. Special thread
			// PLUS: ability to cancel
			DataFetchWorker worker = this.m_ActiveThread;

			// Create a new thread. This will be the active one.
			// all other threads will be discarded when they return from whereever they have been
			worker = new DataFetchWorker(huh, (WinFormThreading.ProgressStateDelegate)this.AdhocQueryProgress, connectionState);
			// register the thread with the user data returned by the callback
			connectionState.UserData = worker;
			// set the params for the action
			try
			{
				// fetchThread.Sql = vb.BuildSql(newState.m_search_text);
				worker.Command = cmd;
				worker.Context = vc;
				worker.Model = this.m_Model;
				// connection string we have
				// this.m_CurrentDataThread = fetchThread;
				worker.KickOffThread();
			}
			catch (NotImplementedException ex)
			{
				this.m_View.InformUser(ex.Message, "Data Fetch Error", true);
			}
			// turn this into a threaded op
			// this.m_Table = this.m_Model.DataSource.BuildTable(this.m_CurrentCommand, vc);
		}

		private void AdhocQueryProgress(WinFormThreading.ThreadProcessState connectionState)
		{
			DataFetchWorker thread = (DataFetchWorker)connectionState.UserData;
			//if (thread != this.m_ActiveThread) return;

			if (connectionState.IsErrored)
			{
				this.m_View.InformUser(connectionState.Exception.Message, "Data fetch error", true);
				this.m_View.AdhocQueryComplete(null);
			}
			if (connectionState.IsDone)
			{
				// get the dset somehow.
				this.m_View.AdhocQueryComplete(thread.Results);
			}
		}


		bool IsWantedInHistory(Command Command)
		{
			Commands.Cmnd cmd = Command.Cmd;
			return (cmd == Commands.Cmnd.History
				 || cmd == Commands.Cmnd.Templates
				 || cmd == Commands.Cmnd.Profile) ? false : true;
		}

		// Might be useful to know
		bool IsProcessedByPresenter(Command Command)
		{
			Commands.Cmnd cmd = Command.Cmd;
			return (cmd == Commands.Cmnd.History
				 || cmd == Commands.Cmnd.Templates
				 || cmd == Commands.Cmnd.Profile) ? true : false;
		}

		private string GetDataTableName(ViewContext vc)
		{
			// prefer the table name set in the view def
			if (vc.ViewDef != null)
				return vc.ViewDef.TableName;
			// default position 
			string tableName = vc.ObjectName;
			// special case: drilling from table to get table data
			if (this.m_CurrentCommand.Cmd == Commands.Cmnd.TableData && vc.ViewDef == null)
				tableName = this.GetTableName();
			return tableName;
		}

		// ie. show the SQL. This is useful 
		void ShowCommand()
		{
			// get it from the table
			string cmd = GetCurrentCommandString();
			if (cmd.Length == 0)
				this.m_View.InformUser("Unable to provide the command string for this view", "Apologies", false);
			else
			{
				this.m_View.ShowSource("", cmd, "");
			}
		}

		private string GetCurrentCommandString()
		{
			DataTableExtraInfoManager mgr = new DataTableExtraInfoManager(this.m_Table);
			return mgr.Command;
		}

		// 
		void ShowSource()
		{
			ViewContext vc = this.CreateViewContext(this.m_CurrentCommand, "");
			string objectName = this.GetObjectName();
			string source = this.m_Model.DataSource.GetSourceCode(vc, objectName);
			// 
			this.m_View.ShowSource(objectName, source, this.m_SourceSearchText);
		}

		// New generic version: command should contain the object name as a parameter
		void ShowSource(Command command)
		{
			string source = this.m_Model.DataSource.GetSourceCode(command);
			// assumes object name was in column 1.
			string objectName = command[Command.TableParameterName(0)];
			//if (command["TABLE"].Length > 0) objectName = command["TABLE"]; // prefer table name param value?
			string highLightText = command["HIGHLIGHT"];
			if (highLightText.Length > 0) this.m_SourceSearchText = highLightText;
			this.m_View.ShowSource(objectName, source, this.m_SourceSearchText);
		}

		private string FormatXML(XmlDocument doc)
		{
			// Create a stream buffer that can be read as a string
			using (StringWriter sw = new StringWriter())

			// Create a specialized writer for XML code
			using (XmlTextWriter xtw = new XmlTextWriter(sw))
			{
				// Set the writer to use indented (hierarchical) elements
				xtw.Formatting = System.Xml.Formatting.Indented;

				// Write the XML document to the stream
				doc.WriteTo(xtw);

				// Return the stream as a string
				return sw.ToString();
			}
		}

		private void ShowProfileData(Command command)
		{
			string fieldName = this.m_CurrentRowTable.Rows[0][1].ToString();
			string xmlSource = this.m_CurrentRowTable.Rows[0][2].ToString();
			string altSourceCol = command["DATACOL"];
			if (altSourceCol.Length > 0)
			{
				int sourceCol = Convert.ToInt32(altSourceCol);
				xmlSource = this.m_CurrentRowTable.Rows[0][altSourceCol].ToString();
			}
			// pretty print it.
			try
			{
				XmlDocument doc = new XmlDocument();
				doc.LoadXml(xmlSource);
				xmlSource = this.FormatXML(doc);
			}
			catch (SystemException exc)
			{
				string message = exc.Message;
			}
			// capture error if not Xml.
			this.m_View.ShowSource(fieldName, xmlSource, "");
		}


		DataTable TableFromCurrentRow(bool DataOnly)
		{
			// two columns
			DataTable dt = DataTableHelpers.MakeDataTable("Profile", "Seq|Field Name|Data", "ISS");
			if (this.m_CurrentRowTable.Rows.Count == 0) return dt;
			DataRow dr = this.m_CurrentRowTable.Rows[0];
			for (int idx = 0; idx < dr.ItemArray.GetLength(0); ++idx)
			{
				DataRow profileRow = dt.NewRow();
				profileRow[0] = idx + 1;
				profileRow[1] = this.m_CurrentRowTable.Columns[idx].ColumnName;
				profileRow[2] = dr[idx];
				if (DataOnly && profileRow[2].ToString().Length == 0)
					idx = idx + 0; // skip
				else
					dt.Rows.Add(profileRow);
			}
			// this.m_CurrentRowTable;
			return dt;
		}

		// set default edit save state
		bool SaveRequiresConfirmation()
		{
			// Duff idea. Needs more development.
			return true;
			// a change requires confirmation unless it is a development database
			// return (Model.DataSource.IsDevelopmentDatabase == false);
		}

		// return true to continue: false to abort.
		bool CommitEdits(bool requireConfirmToSave)
		{
			// tidy up any unsaved controls
			if (!this.m_View.FinaliseEdits()) return false; // error prevents continuation

			// not if not dirty
			if (!this.m_EditState.Dirty) return true;

			int response = 1; // assume ok to save

			// handle confirm logic
			if (requireConfirmToSave)
			{
				response = this.m_View.AskUser("Save edits", "Confirm save", true);
				// handle cancel ie. return NOT ok to continue current op.
				if (response == -1) return false;
			}
			// 0 = no save so quit this routine now and return ok to continue
			if (response == 0)
			{
				// prevent being asked to save twice in some cases
				this.m_EditState.Data.RejectChanges();
				return true;
			}
			// [Normal] save wanted to try it
			string message;
			if (!this.m_Model.DataSource.Update(this.m_EditState.Data, out message))
			{
				// Some error
				this.m_View.InformUser(message, "Error during update", true);
				// abandon op to enable edits to be corrected if possible.
				return false;
			}
			// edits were saved apparently so ok to proces
			return true;
		}

	}

	/*
		Refactoring. This holds presenter shared state. Originally there was only one 
		presenter to drive the form. When tabs came along this changed. This class 
		was developed to handle cases where the presenters share state. The 
		first identified use is the search text list source.
	*/
	class PresenterSharedState // catchy name!
	{
		static PresenterSharedState _g = new PresenterSharedState();
		internal static PresenterSharedState instance { get { return _g; } }

		public List<String> SearchList { get { return this.m_SearchList; } }

		PresenterSharedState()
		{
			this.m_SearchList = new List<String>();
		}
		List<String> m_SearchList;
	}

	/*
		Class to handle the editing dirty (ha ha) work.
	*/
	class EditState
	{
		// construct with the DataTable containing the grid data
		public EditState(DataTable dt)
		{
			this.m_DataTable = dt;
		}

		public DataTable Data { get { return this.m_DataTable; } }
		public bool Dirty
		{
			get
			{
				if (this.m_DataTable == null) return false; // unused state
				// check rowstate and return true = dirty when any
				// non-unchanged row is encountered.
				foreach (DataRow dr in this.m_DataTable.Rows)
					if (dr.RowState != DataRowState.Unchanged)
						return true;
				return false;
			}
		}
		DataTable m_DataTable;
	}
}
